On this page

Skip to content

Using Dependency Injection in WPF MVVM Applications

Although WPF can be written in an MVVM architecture without additional packages, as a web developer, I prefer not to spend too much time researching how to build the MVVM architecture from scratch. Therefore, I recommend using Microsoft's "CommunityToolkit.Mvvm" package for development. For more information on this package, you can refer to the "MVVM Toolkit Introduction".

Using Dependency Injection in WPF

App.xaml

Here we configure the DI package used by the project. We use "Microsoft.Extensions.DependencyInjection" as an example (it is not installed by default in WPF). If you need to read settings from "appsettings.json", you will need to install "Microsoft.Extensions.Configuration" and other relevant packages. This example includes the following packages:

  • CommunityToolkit.Mvvm
  • Microsoft.Extensions.Configuration
  • Microsoft.Extensions.Configuration.Abstractions
  • Microsoft.Extensions.Configuration.FileExtensions
  • Microsoft.Extensions.Configuration.Json
  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.Options.ConfigurationExtensions
csharp
public partial class App : Application {
    protected override void OnStartup(StartupEventArgs e) {
        IConfigurationBuilder builder = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);

        IConfiguration configuration = builder.Build();

        ServiceCollection serviceCollection = new ServiceCollection();
        ConfigureServices(serviceCollection, configuration);

        ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();

        MainWindow mainWindow = serviceProvider.GetRequiredService<MainWindow>()!;
        mainWindow.Show();
    }

    private static void ConfigureServices(IServiceCollection services, IConfiguration configuration) {
        services.Configure<AppOptions>(configuration!.GetSection("App"));
        services.AddTransient<MainWindow>();
        services.AddTransient<ViewModel>();
    }
}

The StartupUri attribute of <Application /> is used to specify the first window to create and display after the application starts. If StartupUri="MainWindow.xaml" is specified in XAML, the WPF framework will use that XAML file to create an instance of the MainWindow class. If a constructor with parameters is defined in MainWindow, a "no matching constructor found" error message will occur. Since the MainWindow class is now created using DI and includes a constructor with injected types, the StartupUri attribute specified in <Application /> must be removed.

xml
<Application x:Class="TPI.Softeare.EntityGenerator.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:TPI.Softeare.EntityGenerator">
    <Application.Resources>

    </Application.Resources>
</Application>

Building a WPF MVVM Application

ViewModel

When creating a ViewModel, keep the following in mind:

  • The ViewModel must inherit from ObservableObject.
  • The ViewModel must be declared as a partial class.
  • Properties intended for data binding with the UI should be created as fields and decorated with the ObservableProperty attribute. It is recommended to use lower camelCase for naming.
  • Command methods to be executed should be decorated with the RelayCommand attribute.
csharp
public partial class ViewModel : ObservableObject {
    [ObservableProperty]
    private string? input;

    [RelayCommand]
    private void Submit() {
        MessageBox.Show("Input value: " + Input);
        Input += "_Modified";
        MessageBox.Show("Changed value: " + Input);
    }
}

Looking at the example above, you might find it strange that we declared a lowercase input, but used the capitalized Input inside Submit(). If you hover over the attribute, you can see in the documentation that it automatically generates additional code; this is also why the class must use the partial modifier.

ObservableProperty attribute documentation.

observable property attribute

RelayCommand attribute documentation.

relay command attribute

MainWindow.xaml

Set the DataContext to the ViewModel in the constructor. If using DI, the ViewModel can be passed in as a parameter.

csharp
public partial class MainWindow : Window {
    public MainWindow(ViewModel viewModel) {
        InitializeComponent();
        DataContext = viewModel;
    }
}
  • The <TextBox /> uses Text="{Binding Input}" to bind the display text. Since it is actually binding to the property automatically generated by the attribute, you must use PascalCase.
  • The <Button /> uses Command="{Binding SubmitCommand}" to bind the method executed upon clicking. Since it is actually binding to the property automatically generated by the attribute, the method name must have the "Command" suffix.
xml
<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBox HorizontalAlignment="Left" Margin="100,50,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="150" Text="{Binding Input}"/>
        <Button Content="Submit" HorizontalAlignment="Left" Margin="100,100,0,0" VerticalAlignment="Top" Width="150" Command="{Binding SubmitCommand}"/>

    </Grid>
</Window>

Execution Results

After entering "Test" in the TextBox, click "Submit".

wpf di demo input

The Submit() method executes successfully, and the value of Input is the value we entered in the TextBox.

wpf di demo success

When the value of Input is changed inside Submit(), the content of the TextBox on the screen updates accordingly.

wpf di demo update

Change Log

  • 2023-02-15 Initial version created.